/**
 * 190112:规范化后未经测试
 * 200910:增加用户事件没有释放警告相关代码
 * 210619:增加旋转和平移操作互换选项
 * 210816:升级three.js到r131
 * 210907:完成【210619】在移动端的适配，并对陈旧代码做了改善
 * 211126:用wheel事件替换已被弃用的非标准mousewheel事件
 * 220124:相机变换从第二次move开始（因VR模式下start和move有较大间隔）
 * 220208:从上一版库中抽取，修改为构造函数指定移动事件对象moveEvent,修改获取起始交互事件对象crossEvent为startCrossEvent
 * 220811:add parameter [isBreak] to method [setTo];
 *        check camera info changed or not in method [setTo];
 */

import {Vector3,EventDispatcher,Spherical,Quaternion,Vector2} from 'three';
import {Props} from '../general/props';

const DEFAULT_UP=new Vector3(0,1,0);
const STATE = {NONE: 0, ROTATE: 1, DOLLY: 2, PAN: 3, DOLLY_PAN: 4,DOLLY_ROTATE:5};
const EPSILON=1e-6;
const CLAMP=(v,min,max)=>{
    return Math.max(min, Math.min(max, v))
};

/**
 * 摄像机控制
 */
export default class CameraController{
    constructor(camera, viewPort, startEvent,moveEvent,values={}) {
        this._camera = camera;
        this._crossEvent = startEvent;
        this._moveCrossEvent=moveEvent;
        this._eventCenter = new EventDispatcher();
        this._target = new Vector3();
        this._rect= viewPort !== undefined ?
            {left:viewPort.left, top:viewPort.top, width:viewPort.width, height:viewPort.height,}:
            {left: 0, top: 0, width: window.innerWidth, height: window.innerHeight};
        this._values=values;
        this.init();
    }

    //------------------------------------------对外属性及方法-----------------------------------------------
    //#region

    /**
     * 获取UI事件对象
     * @returns {eventCenter}
     */
    get startCrossEvent(){
        return this._crossEvent;
    }
    get moveCrossEvent(){
        return this._moveCrossEvent;
    }

    /**
     * 获取事件对象
     * @returns {EventDispatcher}
     */
    get eventCenter(){
        return this._eventCenter;
    }

    /**
     * 获取焦点
     * @returns {Vector3}
     */
    get target(){
        return this._target;
    }

    /**
     * 获取相机
     * @returns {*}
     */
    get camera(){
        return this._camera;
    }

    /**
     * 设置相机
     * @param camera
     */
    set camera(camera){
        this._camera=camera;
        this.update();
        this.fireChangeEvent();
        this.fireShiftCameraEvent();
    }

    /**
     * 获取rect
     * @returns {*|{top: number, left: number, width: *, height: *}}
     */
    get rect(){
        return this._rect;
    }

    /**
     * 设置rect（窗体更新等）
     * rect:{left,top,width,height} or [left,top,width,height]
     * @param rect
     */
    set rect(rect){
        if(Array.isArray(rect)){
            this._rect.left=rect[0];
            this._rect.top=rect[1];
            this._rect.width=rect[2];
            this._rect.height=rect[3];
        }else{
            this._rect.left=rect.left;
            this._rect.top=rect.top;
            this._rect.width=rect.width;
            this._rect.height=rect.height;
        }
        this.update();
        this.fireResizeEvent();
        this.fireChangeEvent();
    }

    /**
     * 设置原始焦点
     * @param pos
     */
    set oriLook(pos){
        this._target.copy(pos);
        this._target0.copy(pos);
        this.update();
    }

    /**
     * 设置原始位置
     * @param pos
     */
    set oriPosition(pos){
        this._camera.position.copy(pos);
        this._position0.copy(pos);
        this.update();
    }

    /**
     * 启用粘滞
     * @param bool
     */
    set damping(bool){
        this._doDamping=bool;
        if(this._doDamping){
            cancelAnimationFrame(this._requests[0]);
            let ani=()=>{
                this.update();
                this._requests[0]=requestAnimationFrame(ani);
            };
            ani();
        }else{
            cancelAnimationFrame(this._requests[0]);
        }
    }

    /**
     * 获取当前状态信息
     * @returns {{focusLength: number, zoom: *, position: *, target: *}}
     */
    get posInfo(){
        return {
            target: new Vector3().copy(this._target),
            position: new Vector3().copy(this._camera.position),
            focusLength: this._spherical.radius,
            zoom: this._camera.zoom,
        };
    }
    set posInfo(posInfo){
        this.setTo(posInfo.target,posInfo.position,posInfo.focusLength,posInfo.zoom);
    }

    /**
     * 获取当前缩放
     * @returns {number|*}
     */
    get currentScale(){
        return this._currentScale;
    }

    /**
     * 获取当前球坐标
     * @returns {Spherical}
     */
    get spherical(){
        return this._spherical;
    }

    /**
     * 使用球坐标设置相机
     * @param spherical
     */
    set spherical(spherical){
        this._sphQuat.setFromUnitVectors(this._camera.up, DEFAULT_UP);
        this._sphQuatInv.copy(this._sphQuat).invert();
        let position = this._camera.position;
        this._sphOffset.copy(position).sub(this._target);
        this._sphOffset.applyQuaternion(this._sphQuat);
        this._sphOffset.setFromSpherical(spherical);
        this._sphOffset.applyQuaternion(this._sphQuatInv);
        position.copy(this._target).add(this._sphOffset);
        this._camera.position.copy(position);
        this._camera.lookAt(this._target);
        if(!this._doDamping)this.update();
        this.fireCustomEvent();
        this._state = STATE.NONE;
    }

    /**
     * 球坐标增量（需手动update）
     * @returns {Spherical}
     */
    get sphericalDelta(){
        return this._sphericalDelta;
    }
    set sphericalDelta(sph){
        this._sphericalDelta.copy(sph);
    }

    /**
     * 是否启用
     */
    get enabled(){
        return this._props.map.enabled.value;
    }
    set enabled(bol){
        this._props.map.enabled.value=bol;
    }

    /**
     * 最小目标距离
     */
    get minDistance(){
        return this._props.map.minDistance.value;
    }
    set minDistance(num){
        this._props.map.minDistance.value=num;
    }

    /**
     * 最大目标距离
     */
    get maxDistance(){
        return this._props.map.maxDistance.value;
    }
    set maxDistance(num){
        this._props.map.maxDistance.value=num;
    }

    /**
     * 最小极角
     */
    get minPolarAngle(){
        return this._props.map.minPolarAngle.value;
    }
    set minPolarAngle(num){
        this._props.map.minPolarAngle.value=num;
    }

    /**
     * 最大极角
     */
    get maxPolarAngle(){
        return this._props.map.maxPolarAngle.value;
    }
    set maxPolarAngle(num){
        this._props.map.maxPolarAngle.value=num;
    }

    /**
     * 最小方位角
     */
    get minAzimuthAngle(){
        return this._props.map.minAzimuthAngle.value;
    }
    set minAzimuthAngle(num){
        this._props.map.minAzimuthAngle.value=num;
    }

    /**
     * 最大方位角
     */
    get maxAzimuthAngle(){
        return this._props.map.maxAzimuthAngle.value;
    }
    set maxAzimuthAngle(num){
        this._props.map.maxAzimuthAngle.value=num;
    }

    /**
     * 是否启用缩放平滑
     */
    get enableScrollSmooth(){
        return this._props.map.enableScrollSmooth.value;
    }
    set enableScrollSmooth(bol){
        this._props.map.enableScrollSmooth.value=bol;
    }

    /**
     * 缩放平滑系数
     */
    get scrollSmoothFactor(){
        return this._props.map.scrollSmoothFactor.value;
    }
    set scrollSmoothFactor(num){
        this._props.map.scrollSmoothFactor.value=num;
    }

    /**
     * 触摸缩放平滑比
     */
    get touchSmoothRatio(){
        return this._props.map.touchSmoothRatio.value;
    }
    set touchSmoothRatio(num){
        this._props.map.touchSmoothRatio.value=num;
    }

    /**
     * 粘滞系数
     */
    get dampingFactor(){
        return this._props.map.dampingFactor.value;
    }
    set dampingFactor(num){
        this._props.map.dampingFactor.value=num;
    }

    /**
     * 是否可缩放
     */
    get enableZoom(){
        return this._props.map.enableZoom.value;
    }
    set enableZoom(bol){
        this._props.map.enableZoom.value=bol;
    }

    /**
     * 缩放速度
     */
    get zoomSpeed(){
        return this._props.map.zoomSpeed.value;
    }
    set zoomSpeed(num){
        this._props.map.zoomSpeed.value=num;
    }

    /**
     * 是否可旋转
     */
    get enableRotate(){
        return this._props.map.enableRotate.value;
    }
    set enableRotate(bol){
        this._props.map.enableRotate.value=bol;
    }

    /**
     * 旋转速度
     */
    get rotateSpeed(){
        return this._props.map.rotateSpeed.value;
    }
    set rotateSpeed(num){
        this._props.map.rotateSpeed.value=num;
    }

    /**
     * 是否可平移
     */
    get enablePan(){
        return this._props.map.enablePan.value;
    }
    set enablePan(bol){
        this._props.map.enablePan.value=bol;
    }

    /**
     * 平移速度
     */
    get panSpeed(){
        return this._props.map.panSpeed.value;
    }
    set panSpeed(num){
        this._props.map.panSpeed.value=num;
    }

    /**
     * 目标范围盒小值
     */
    get targetRangeMin(){
        return this._props.map.targetRangeMin.value;
    }
    set targetRangeMin(vec){
        this._props.map.targetRangeMin.value=vec;
    }

    /**
     * 目标范围盒大值
     */
    get targetRangeMax(){
        return this._props.map.targetRangeMax.value;
    }
    set targetRangeMax(vec){
        this._props.map.targetRangeMax.value=vec;
    }

    /**
     * 超出范围事件释放
     */
    get stopWhenPointerOut(){
        return this._props.map.stopWhenPointerOut.value;
    }
    set stopWhenPointerOut(bol){
        this._props.map.stopWhenPointerOut.value=bol;
    }

    /**
     * 禁止发送事件
     */
    get enableEvent(){
        return this._props.map.enableEvent.value;
    }
    set enableEvent(bol){
        this._props.map.enableEvent.value=bol;
    }

    /**
     * 平移旋转操作交换
     */
    get inversePanRotate(){
        return this._props.map.inversePanRotate.value;
    }
    set inversePanRotate(bol){
        this._props.map.inversePanRotate.value=bol;
    }

    /**
     * 释放挂起事件
     */
    releaseUnfinishedEvent() {
        this.onPointerEnd();
    }

    /**
     * 设置相机到（目标，位置，焦距，倍率，是否中断当前行为）
     * @param target
     * @param position
     * @param focusLength
     * @param zoom
     * @param isBreak
     */
    setTo(target,position,focusLength,zoom,isBreak=true){
        const te=this._target.equals(target);
        const pe=this._camera.position.equals(position);
        if(this._camera.isPerspectiveCamera&&te&&pe)return;

        const ze=this._camera.zoom==zoom;
        if(this._camera.isOrthographicCamera&&te&&pe&&ze)return;

        if(this._camera.isCabinetCamera&&te&&pe&&ze&&fe)return;
        const fe=this._camera.focusLength==focusLength;

        this._target.copy(target).clamp(this._props.map.targetRangeMin.value,this._props.map.targetRangeMax.value);
        this._camera.position.copy(position);
        if(this._camera.isOrthographicCamera)this._camera.zoom = zoom;
        if(this._camera.isCabinetCamera)this._camera.focusLength=focusLength;
        this._camera.updateProjectionMatrix();
        if(!this._doDamping)this.update();
        this.fireCustomEvent();
        if(isBreak)this._state = STATE.NONE;
    }

    /**
     * 重设到初始状态
     */
    reset() {
        this._target.copy(this._target0).clamp(this._props.map.targetRangeMin.value,this._props.map.targetRangeMax.value);
        this._camera.position.copy(this._position0);
        if(this._camera.isOrthographicCamera)this._camera.zoom = this._zoom0;
        if(this._camera.isCabinetCamera)this._camera.focusLength=this._focusLength0;
        this._camera.updateProjectionMatrix();
        if(!this._doDamping)this.update();
        this.fireCustomEvent();
        this._state = STATE.NONE;
    }

    /**
     * 销毁
     */
    dispose() {
        this.releaseProgressEvent();
        if(this._crossEvent.element)this._crossEvent.element.removeEventListener('contextmenu', CameraController.onContextMenu, false);
        this._crossEvent.removeEventListener('leftPointDown', this.onLeftPointDownBind);
        this._crossEvent.removeEventListener('rightPointDown', this.onRightPointDownBind);
        // this._crossEvent.removeEventListener('mouseWheel', this.onMouseWheelBind);211126
        this._crossEvent.removeEventListener('wheel', this.onMouseWheelBind);
        this._props.dispose();
        this._props=null;

    }

    //#endregion
    //------------------------------------------------私有属性及方法--------------------------------------------
    //#region

    static onContextMenu(event) {
        event.preventDefault();
    }
    init(){
        this.initProps();
        this.initVariable();
        this.initEventBind();
        this.addEventListener();
        this.initPropCallback();
        this.update();
    }
    initProps(){
        this._props=new Props({
            enabled: this._values.enabled!==undefined?this._values.enabled:true,//是否启用
            minDistance: this._values.minDistance||0,//最小目标距离
            maxDistance: this._values.maxDistance||Number.POSITIVE_INFINITY,//最大目标距离
            minPolarAngle: this._values.minPolarAngle||0,//最小极角
            maxPolarAngle: this._values.maxPolarAngle||Math.PI,//最大极角
            minAzimuthAngle: this._values.minAzimuthAngle||Number.NEGATIVE_INFINITY,//最小方位角
            maxAzimuthAngle: this._values.maxAzimuthAngle||Number.POSITIVE_INFINITY,//最大方位角
            enableScrollSmooth:this._values.enableScrollSmooth!==undefined?this._values.enableScrollSmooth:true,//是否启用缩放平滑
            scrollSmoothFactor:this._values.scrollSmoothFactor||20,//缩放平滑系数
            touchSmoothRatio:this._values.scrollSmoothFactor||0.5,//触摸缩放平滑比
            dampingFactor: this._values.dampingFactor||0.1,//粘滞系数
            enableZoom: this._values.enableZoom!==undefined?this._values.enableZoom:true,//是否可缩放
            zoomSpeed: this._values.zoomSpeed||1,//缩放速度
            enableRotate: this._values.enableRotate!==undefined?this._values.enableRotate:true,//是否可旋转
            rotateSpeed: this._values.rotateSpeed||1,//旋转速度
            enablePan: this._values.enablePan!==undefined?this._values.enablePan:true,//是否可平移
            panSpeed:this._values.panSpeed||1,//平移速度
            targetRangeMin:this._values.targetRangeMin||new Vector3(-50,-50,-50),//目标范围盒小值
            targetRangeMax:this._values.targetRangeMax||new Vector3(50,50,50),//目标范围盒大值
            stopWhenPointerOut:this._values.stopWhenPointerOut!==undefined?this._values.stopWhenPointerOut:true,//超出边界事件释放
            enableEvent:this._values.enableEvent!==undefined?this._values.enableEvent:true,//禁止发送事件
            inversePanRotate:this._values.inversePanRotate!==undefined?this._values.inversePanRotate:false,//平移旋转操作互换
        });
    }
    initVariable(){
        this._target0 = this._target.clone();
        this._position0 = this._camera.position.clone();
        this._zoom0 = this._camera.zoom;
        this._focusLength0=this._camera.position.distanceTo(this._target0);
        this._state = STATE.NONE;
        this._spherical = new Spherical();
        this._oriSphericalRadius = 0;
        this._sphericalDelta = new Spherical(0,0,0);
        this._currentScale = 1;
        this._changeEvent = {type: 'change'};
        this._startEvent = {type: 'start',paras:{pointer:null}};
        this._endEvent = {type: 'end',paras:{detail:null,state:-1}};
        this._panEvent = {type: 'pan'};
        this._rotateEvent = {type: 'rotate'};
        this._scaleEvent = {type: 'scale',paras:{zoom: 1}};
        this._shiftCameraEvent={type:'shiftCamera'};
        this._resizeEvent={type:'resize'};
        this._customEvent={type:'custom'};
        this._panOffset = new Vector3();
        this._lastTarget=new Vector3();
        this._lastPosition = new Vector3();
        this._lastQuaternion = new Quaternion();
        this._rotateStart = new Vector2();
        this._quat=new Quaternion().setFromUnitVectors(this._camera.up, DEFAULT_UP);
        this._rotateEnd = new Vector2();
        this._rotateDelta = new Vector2();
        this._panStart = new Vector2();
        this._panEnd = new Vector2();
        this._curZoomDelta=0;
        this._endZoomDelta=0;
        this._panDelta = new Vector2();
        this._dollyStart = new Vector2();
        this._dollyEnd = new Vector2();
        this._dollyDelta = new Vector2();
        this._requests=[null,null];
        this._doDamping=false;
        this._sphOffset=new Vector3();
        this._userEventClear=true;
        this._sphQuat=new Quaternion();
        this._sphQuatInv=new Quaternion();
        this._uptOffset=new Vector3();
        this._uptQuatInv=new Quaternion();
        this._tV0=new Vector3();
    }
    initPropCallback(){
        this._props.onKeyChange('enableScrollSmooth',(_new,_old)=>{
            if(!_new){
                cancelAnimationFrame(this._requests[1]);
            }
        });
        this._props.onKeyChange('stopWhenPointerOut',(_new,_old)=>{
            this.releaseUnfinishedEvent();
        });
    }
    initEventBind(){
        this.onLeftPointDownBind=(e)=>{
            this.onLeftPointDown(e);
        };
        this.onRightPointDownBind=(e)=>{
            this.onRightPointDown(e);
        };
        this.onMouseWheelBind=(e)=>{
            this.onMouseWheel(e);
        };
        this.onMouseMoveBind=(e)=>{
            this.onMouseMove(e);
        };
        this.onMouseUpBind=(e)=>{
            this.onMouseUp(e);
        };
        this.onTouchMoveBind=(e)=>{
            this.onTouchMove(e);
        };
        this.onTouchEndBind=(e)=>{
            this.onTouchEnd(e);
        };
        this.fireCustomEvent=()=>{
            this._eventCenter.dispatchEvent(this._customEvent);
        };
        this.fireResizeEvent=()=>{
            this._eventCenter.dispatchEvent(this._resizeEvent);
        };
        this.fireChangeEvent=()=>{
            this._eventCenter.dispatchEvent(this._changeEvent);
        };
        this.fireShiftCameraEvent=()=>{
            this._eventCenter.dispatchEvent(this._shiftCameraEvent);
        };
        this.firePanEvent=()=>{
            this._eventCenter.dispatchEvent(this._panEvent);
        };
        this.fireRotateEvent=()=>{
            this._eventCenter.dispatchEvent(this._rotateEvent);
        };
        this.fireScaleEvent=()=>{
            this._scaleEvent.paras.zoom=this._currentScale;
            this._eventCenter.dispatchEvent(this._scaleEvent);
        };
        this.fireStartEvent=(event)=>{
            this._startEvent.paras.pointer=event;
            this._eventCenter.dispatchEvent(this._startEvent);
        };
        this.fireEndEvent=(detail,state)=>{
            this._endEvent.paras.detail=detail;
            this._endEvent.paras.state=state;
            this._eventCenter.dispatchEvent(this._endEvent);
        };
    }
    update() {
        let isChange=false;
        this._uptQuatInv.copy(this._quat).invert();
        let position = this._camera.position;
        this._uptOffset.copy(position).sub(this._target);
        this._uptOffset.applyQuaternion(this._quat);
        this._spherical.setFromVector3(this._uptOffset);
        if (!this._oriSphericalRadius) this._oriSphericalRadius = this._spherical.radius;
        this._spherical.theta += this._sphericalDelta.theta;
        this._spherical.phi += this._sphericalDelta.phi;
        this._spherical.radius+=this._sphericalDelta.radius;
        this._spherical.theta = CLAMP(this._spherical.theta,this._props.map.minAzimuthAngle.value,this._props.map.maxAzimuthAngle.value);
        this._spherical.phi =CLAMP(this._spherical.phi,this._props.map.minPolarAngle.value,this._props.map.maxPolarAngle.value);
        this._spherical.radius = Math.max(this._props.map.minDistance.value, Math.min(this._props.map.maxDistance.value, this._spherical.radius));
        this._spherical.makeSafe();
        this._currentScale=this._oriSphericalRadius/this._spherical.radius;
        if (this._camera.isOrthographicCamera) {
            if(this._camera.zoom !==this._currentScale) {
                this._camera.zoom = this._currentScale;
                if (this._camera.focusLength) this._camera.focusLength = this._spherical.radius;
                this._camera.updateProjectionMatrix();
            }
        }
        this._target.add(this._panOffset).clamp(this._props.map.targetRangeMin.value,this._props.map.targetRangeMax.value);
        this._uptOffset.setFromSpherical(this._spherical);
        this._uptOffset.applyQuaternion(this._uptQuatInv);
        position.copy(this._target).add(this._uptOffset);
        this._camera.lookAt(this._target);
        if (this._doDamping === true) {
            let k = 1 - this._props.map.dampingFactor.value;
            if (this._props.map.enableRotate.value) {
                this._sphericalDelta.theta *= k;
                this._sphericalDelta.phi *= k;
            }
            if (this._props.map.enableZoom.value) {
                this._sphericalDelta.radius *= k;
            }
            if (this._props.map.enablePan.value) {
                this._panOffset.multiplyScalar(k);
            }
        } else {
            this._sphericalDelta.set(0, 0, 0);
            this._panOffset.set(0, 0, 0);
        }
        if(this._props.map.enableEvent.value) {
            if (this._lastTarget.distanceToSquared(this._target) > EPSILON) {
                isChange = true;
                this.firePanEvent();
            }
            if (Math.abs(this._camera.position.distanceToSquared(this._target) - this._lastPosition.distanceToSquared(this._lastTarget)) > EPSILON) {
                isChange = true;
                this.fireScaleEvent();
            }
            if (8 * (1 - this._lastQuaternion.dot(this._camera.quaternion)) > EPSILON) {
                isChange = true;
                this.fireRotateEvent();
            }
            if (isChange) {
                this.fireChangeEvent();
                this._lastPosition.copy(this._camera.position);
                this._lastQuaternion.copy(this._camera.quaternion);
                this._lastTarget.copy(this._target);
            }
        }
    }
    checkAvailable(point0, point1) {
        let v0=point1?point0:point0.clientX;
        let v1=point1?point1:point0.clientY;
        if (v0 < this._rect.left || v0 > (this._rect.left + this._rect.width))return false;
        return !(v1 < this._rect.top || v1 > (this._rect.top + this._rect.height));
    }
    addEventListener() {
        if(this._crossEvent.element)this._crossEvent.element.addEventListener('contextmenu', CameraController.onContextMenu, false);
        this._crossEvent.addEventListener('leftPointDown', this.onLeftPointDownBind);
        this._crossEvent.addEventListener('rightPointDown', this.onRightPointDownBind);
        // this._crossEvent.addEventListener('mouseWheel', this.onMouseWheelBind);211126
        this._crossEvent.addEventListener('wheel', this.onMouseWheelBind);
    }
    onRightPointDown(event) {
        if (event.isMouse) {
            if (!this.checkAvailable(event))return;
            this._state=this.inversePanRotate?STATE.ROTATE:STATE.PAN;
            this.onMouseDown(event);
        } else if (event.isTouch) {
            for(let point of event.touches){
                if(!this.checkAvailable(point))return;
            }
            this.onTouchStart(event);
        }
    }
    onLeftPointDown(event) {
        if (event.isMouse) {
            if (!this.checkAvailable(event))return;
            this._state=this.inversePanRotate?STATE.PAN:STATE.ROTATE;
            this.onMouseDown(event);
        } else if (event.isTouch) {
            for(let point of event.touches){
                if(!this.checkAvailable(point))return;
            }
            this.onTouchStart(event);
        }
    }
    onMouseWheel(event) {
        if (this._props.map.enabled.value === false || this._props.map.enableZoom.value === false )return;
        if (!this.checkAvailable(event))return;
        this.mouseWheelDolly(event);
        this.fireStartEvent(event);
        this.fireCustomEvent();
    }
    mouseWheelDolly(event){
        let delta = 0;
        if (event.deltaY !== null) {
            delta = event.deltaY;
        } else if (event['wheelDelta'] !== null) {//webkit/Opera/Explorer 9
            delta = event['wheelDelta'];
        } else if (event.detail !== undefined) {//fire fox
            delta = -event.detail;
        }
        if(delta===0)return;
        if(this._props.map.enableScrollSmooth.value) {
            this.smoothDolly(event,delta,this._props.map.scrollSmoothFactor.value);
        } else {
            if (delta < 0) {
                this.dollyOut(this.getZoomScale());
            } else if (delta > 0) {
                this.dollyIn(this.getZoomScale());
            }
            if(!this._doDamping)this.update();
            this.fireEndEvent(event,STATE.DOLLY);
        }
    }
    smoothDolly(event,delta,smoothFactor){
        cancelAnimationFrame(this._requests[1]);
        this._endZoomDelta += delta;
        this.animate = () => {
            let uDelta = this._endZoomDelta / smoothFactor;
            this._curZoomDelta += uDelta;
            if ((this._endZoomDelta >= 0 && this._curZoomDelta >= this._endZoomDelta) || (this._endZoomDelta <= 0 && this._curZoomDelta <= this._endZoomDelta)) {
                this._endZoomDelta = 0;
                this._curZoomDelta = 0;
                this.fireEndEvent(event,STATE.DOLLY);
                return;
            }
            let speed = uDelta / delta * this._props.map.zoomSpeed.value;
            if (uDelta < 0) {
                this.dollyOut(this.getZoomScale(speed));
            } else if (uDelta > 0) {
                this.dollyIn(this.getZoomScale(speed));
            }
            if(!this._doDamping)this.update();
            this._requests[1] = requestAnimationFrame(this.animate);
        };
        this.animate();
    }
    onMouseDown(event) {
        if (this._props.map.enabled.value === false)return;
        if (this._state===STATE.ROTATE) {
            if (this._props.map.enableRotate.value === false){
                this._state=STATE.NONE;
            }else{
                this._rotateStart.set(event.clientX, event.clientY);
            }
        } else if (this._state===STATE.PAN) {
            if (this._props.map.enablePan.value === false){
                this._state=STATE.NONE;
            }else{
                this._panStart.set(event.clientX, event.clientY);
            }
        }
        if (this._state !== STATE.NONE) {
            if(!this._userEventClear){
                console.warn('use [releaseUnfinishedEvent] to clear user event before new action!');
                return;
            }
            this._userEventClear=false;
            this.releaseProgressEvent();
            this.appendProgressEvent(true);

            this.fireStartEvent(event);
        }
    }
    setTouchSinglePoint(out,event) {
        let inv=this.inversePanRotate;
        if((!inv)&&(!this._props.map.enableRotate.value))return STATE.NONE;
        if((inv)&&(!this._props.map.enablePan.value))return STATE.NONE;
        if(out)out.set(event.touches[0].clientX, event.touches[0].clientY);
        return inv?STATE.PAN:STATE.ROTATE;
    }
    setTouchDoublePoint(out,outDolly,event){
        let inv=this.inversePanRotate;
        let zom=this._props.map.enableZoom.value;
        let pan=this._props.map.enablePan.value;
        let rot=this._props.map.enableRotate.value;
        if((!inv)&&(!zom)&&(!pan))return STATE.NONE;
        if(inv&&(!zom)&&(!rot))return STATE.NONE;
        if(zom){
            if(outDolly){
                let dx = event.touches[0].clientX - event.touches[1].clientX;
                let dy = event.touches[0].clientY - event.touches[1].clientY;
                let distance = Math.sqrt(dx * dx + dy * dy);
                outDolly.set(0, distance);
            }
            if((!inv)&&(!pan))return STATE.DOLLY;
            if(inv&&(!rot))return STATE.DOLLY;
        }
        if(out){
            out.set((event.touches[0].clientX + event.touches[1].clientX) / 2, (event.touches[0].clientY + event.touches[1].clientY) / 2);
        }
        if(inv){
            return zom?STATE.DOLLY_ROTATE:STATE.ROTATE;
        }else{
            return zom?STATE.DOLLY_PAN:STATE.PAN;
        }
    }
    onTouchStart(event) {
        if (this._props.map.enabled.value === false)return;
        switch (event.touches.length) {
            case 1:
                this._state=this.setTouchSinglePoint(this.inversePanRotate?this._panStart:this._rotateStart,event);
                break;
            case 2:
                this._state=this.setTouchDoublePoint(this.inversePanRotate?this._rotateStart:this._panStart,this._dollyStart,event);
                break;
            default:
                this._state = STATE.NONE;
        }
        if (this._state !== STATE.NONE) {
            this.releaseProgressEvent();
            this.appendProgressEvent(false);

            this.fireStartEvent(event);
        }
    }
    appendProgressEvent(isMouse){
        this._moveCrossEvent.addEventListener('leftPointMove', isMouse?this.onMouseMoveBind:this.onTouchMoveBind);
        this._moveCrossEvent.addEventListener('leftPointUp', isMouse?this.onMouseUpBind:this.onTouchEndBind);
        this._moveCrossEvent.addEventListener('rightPointMove', isMouse?this.onMouseMoveBind:this.onTouchMoveBind);
        this._moveCrossEvent.addEventListener('rightPointUp', isMouse?this.onMouseUpBind:this.onTouchEndBind);
    }
    releaseProgressEvent(){
        this._moveCrossEvent.removeEventListener('leftPointMove', this.onTouchMoveBind);
        this._moveCrossEvent.removeEventListener('leftPointUp', this.onTouchEndBind);
        this._moveCrossEvent.removeEventListener('rightPointMove', this.onTouchMoveBind);
        this._moveCrossEvent.removeEventListener('rightPointUp', this.onTouchEndBind);
        this._moveCrossEvent.removeEventListener('leftPointMove', this.onMouseMoveBind);
        this._moveCrossEvent.removeEventListener('leftPointUp', this.onMouseUpBind);
        this._moveCrossEvent.removeEventListener('rightPointMove', this.onMouseMoveBind);
        this._moveCrossEvent.removeEventListener('rightPointUp', this.onMouseUpBind);
    }
    onMouseMove(event) {
        if (this._props.map.enabled.value === false)return;
        if(this.stopWhenPointerOut){
            if(!this.checkAvailable(event)){
                this.releaseUnfinishedEvent();
                return;
            }
        }
        this.fireCustomEvent();
        if (this._state === STATE.ROTATE) {
            this.mouseMoveRotate(event);
        } else if (this._state === STATE.PAN) {
            this.mouseMovePan(event);
        }
    }
    mouseMovePan(event){
        this._panEnd.set(event.clientX, event.clientY);
        this.movePan();
    }
    movePan(){
        /**
         * 20220124
         */
        if(!this._movePrg){
            this._movePrg=true;
            this._rotateStart.copy(this._rotateEnd);
        }

        this._panDelta.subVectors(this._panEnd, this._panStart);
        this.pan(this._panDelta.x, this._panDelta.y);
        this._panStart.copy(this._panEnd);
        if(!this._doDamping)this.update();
    }
    mouseMoveRotate(event){
        this._rotateEnd.set(event.clientX, event.clientY);
        this.moveRotate();
    }
    moveRotate(){
        /**
         * 20220124
         */
        if(!this._movePrg){
            this._movePrg=true;
            this._rotateStart.copy(this._rotateEnd);
        }

        this._rotateDelta.subVectors(this._rotateEnd, this._rotateStart);
        this.rotateLeft(2 * Math.PI * this._rotateDelta.x / this._rect.width * this._props.map.rotateSpeed.value);
        this.rotateUp(2 * Math.PI * this._rotateDelta.y / this._rect.height * this._props.map.rotateSpeed.value);
        this._rotateStart.copy(this._rotateEnd);
        if(!this._doDamping)this.update();
    }
    onTouchMove(event) {
        if (this._props.map.enabled.value === false)return;
        if(this.stopWhenPointerOut){
            for(let point of event.touches){
                if(!this.checkAvailable(point)){
                    this.releaseUnfinishedEvent();
                    return;
                }
            }
        }
        this.fireCustomEvent();
        switch (event.touches.length) {
            case 1:
                if(this.inversePanRotate){
                    if (this._state === STATE.PAN)this.touchMovePan(event);
                }else{
                    if (this._state === STATE.ROTATE)this.touchMoveRotate(event);
                }
                break;
            case 2:
                if(this.inversePanRotate){
                    if (this._state === STATE.DOLLY_ROTATE||this._state === STATE.DOLLY)this.touchMoveDolly(event);
                    if (this._state===STATE.DOLLY_ROTATE||this._state === STATE.ROTATE) this.touchMoveRotate(event);
                }else{
                    if (this._state === STATE.DOLLY_PAN||this._state === STATE.DOLLY) this.touchMoveDolly(event);
                    if (this._state===STATE.DOLLY_PAN||this._state === STATE.PAN) this.touchMovePan(event);
                }
                break;
            default:
                this._state = STATE.NONE;
        }
    }
    touchMovePan(event){
        this.inversePanRotate?this.setTouchSinglePoint(this._panEnd,event):this.setTouchDoublePoint(this._panEnd,null,event);
        this.movePan();
    }
    touchMoveDolly(event){
        this.setTouchDoublePoint(null,this._dollyEnd,event);
        this._dollyDelta.subVectors(this._dollyEnd, this._dollyStart);
        if (Math.abs(this._dollyDelta.y) / this._dollyStart.y > 0.1) {
            if(this._props.map.enableScrollSmooth.value){
                this.smoothDolly(event,-this._dollyDelta.y,this._props.map.scrollSmoothFactor.value*this._props.map.touchSmoothRatio.value);
            }else{
                if (this._dollyDelta.y > 0) {
                    this.dollyOut(this.getZoomScale());
                } else if (this._dollyDelta.y < 0) {
                    this.dollyIn(this.getZoomScale());
                }
                if (!this._doDamping) this.update();
            }
            this._dollyStart.copy(this._dollyEnd);
        }
    }
    touchMoveRotate(event){
        this.inversePanRotate?this.setTouchDoublePoint(this._rotateEnd,null,event):this.setTouchSinglePoint(this._rotateEnd,event);
        this.moveRotate();
    }
    pan(deltaX, deltaY) {
        if (this._camera.isPerspectiveCamera) {
            let position = this._camera.position;
            this._tV0.copy(position).sub(this._target);
            let targetDistance = this._tV0.length();
            targetDistance *= Math.tan((this._camera.fov / 2) * Math.PI / 180);
            this.panLeft(2 * deltaX * targetDistance / this._rect.height, this._camera.matrix);
            this.panUp(2 * deltaY * targetDistance / this._rect.height, this._camera.matrix);
        } else if (this._camera.isOrthographicCamera) {
            this.panLeft(deltaX * (this._camera.right - this._camera.left) / this._camera.zoom / this._rect.width, this._camera.matrix);
            this.panUp(deltaY * (this._camera.top - this._camera.bottom) / this._camera.zoom / this._rect.height, this._camera.matrix);
        } else {
            console.warn('WARNING:CameraController encountered an unknown camera type -controller disabled.');
            this._props.map.enabled.value=false;
        }
    }
    panAction(dis,matrix,col){
        this._tV0.setFromMatrixColumn(matrix, col);
        this._tV0.multiplyScalar(dis);
        this._panOffset.add(this._tV0.multiplyScalar(this._props.map.panSpeed.value));
    }
    panUp(distance, objectMatrix) {
        this.panAction(distance,objectMatrix,1);
    }
    panLeft(distance, objectMatrix) {
        this.panAction(-distance,objectMatrix,0);
    }
    rotateLeft(angle) {
        this._sphericalDelta.theta -= angle;
    }
    rotateUp(angle) {
        this._sphericalDelta.phi -= angle;
    }
    dolly(scale){
        if (this._camera.isPerspectiveCamera||this._camera.isOrthographicCamera) {
            this._sphericalDelta.radius=this._spherical.radius*(scale-1);
        } else {
            console.warn('WARNING:CameraController encountered an unknown camera type -controller disabled');
            this._props.map.enabled.value=false;
        }
    }
    dollyOut(dollyScale) {
        this.dolly(dollyScale);
    }
    dollyIn(dollyScale) {
        this.dolly(1/dollyScale);
    }
    getZoomScale(speed) {
        let sp=speed?speed:this._props.map.zoomSpeed.value;
        return Math.pow(0.95,sp);
    }
    onMouseUp(event) {
        this.onPointerEnd(event);
    }
    onTouchEnd(event) {
        if(event.touches.length)return;
        this.onPointerEnd(event);
    }
    onPointerEnd(event){
        if (this._props.map.enabled.value === false)return;
        this.releaseProgressEvent();

        this.fireEndEvent(event,this._state);
        this._state = STATE.NONE;
        this._userEventClear=true;
        /**
         * 20220124
         */
        this._movePrg=false;
    }

    //#endregion
}